/*
 * Copyright (c) 2007 Compl Yue Still, All Rights Reserved.
 */

package hbi.wire.java.host;

import hbi.java.util.DataCompat;
import hbi.wire.ProtocolError;

import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;

class DataTagList implements IncomingTag
{

    private final Object values;

    private final Class<?>[] expectedTypes;

    public DataTagList(Class<?>[] expectedTypes)
    {
        assert expectedTypes != null;
        assert expectedTypes.length > 0;
        this.expectedTypes = expectedTypes;
        this.values = new Object[expectedTypes.length];
    }

    public DataTagList(Class<?> expectedComponentType, int length)
    {
        assert length > 0;
        this.expectedTypes = new Class<?>[length];
        for (int i = 0; i < length; i++)
            expectedTypes[i] = expectedComponentType;
        this.values = Array.newInstance(expectedComponentType, length);
    }

    public Object getTagValues()
    {
        return values;
    }

    private int read = 0;

    private DataTag currentTag = null;

    public boolean processIncomingData(ByteBuffer buffer) throws IOException
    {
        assert read < Array.getLength(values) : "called after finished";

        tag_scan: while (buffer.hasRemaining())
        {
            if (currentTag == null)
            {
                int tag = buffer.get() & 0xFF;
                switch (tag)
                {
                case 'N': // null
                    if (expectedTypes[read] != null
                        && expectedTypes[read].isPrimitive())
                        throw new ProtocolError(
                            "Unexpected null for primitive data.");
                    // it's by default null, so doesn't need:
                    // Array.set(values, read, null);
                    if (++read >= Array.getLength(values)) return true;
                    continue tag_scan;
                case 'T': // boolean true
                    Array.set(values, read, DataCompat.castValue(
                        expectedTypes[read], Boolean.TRUE));
                    if (++read >= Array.getLength(values)) return true;
                    continue tag_scan;
                case 'F': // boolean false
                    Array.set(values, read, DataCompat.castValue(
                        expectedTypes[read], Boolean.FALSE));
                    if (++read >= Array.getLength(values)) return true;
                    continue tag_scan;
                case 'I': // 32-bits integer
                    currentTag = new Int32();
                    break;
                case 'L': // 64-bits integer
                    currentTag = new Int64();
                    break;
                case 'D': // 64-bits floating point number
                    currentTag = new FP64();
                    break;
                case 's': // non-final string chunk
                    currentTag = new UTF8Chunks(false);
                    break;
                case 'S': // final string chunk
                    currentTag = new UTF8Chunks(true);
                    break;
                case 'b': // non-final binary chunk
                    currentTag = new BinaryChunks(false);
                    break;
                case 'B': // final binary chunk
                    currentTag = new BinaryChunks(true);
                    break;
                case 'd': // 64-bits UTC date
                    currentTag = new UTCDate();
                    break;
                case '[': // typed array
                    if (expectedTypes[read] != null
                        && !expectedTypes[read].isArray())
                        throw new SecurityException("Unexpected array!");
                    currentTag = new TypedArray(expectedTypes[read]);
                    break;
                case ']': // untyped array
                    if (expectedTypes[read] != null
                        && !expectedTypes[read].isArray())
                        throw new SecurityException("Unexpected array!");
                    currentTag = new UntypedArray(expectedTypes[read]);
                    break;
                default:
                    throw new ProtocolError("Bad data tag: " + tag);
                }
            }
            if (currentTag.processIncomingData(buffer))
            {
                Object argValue = currentTag.getTagValue();
                Array.set(values, read, DataCompat.castValue(
                    expectedTypes[read], argValue));
                currentTag = null;
                if (++read >= Array.getLength(values)) return true;
            }
            else
            // insufficient input data to finish current tag
            {
                break tag_scan;
            }
        }
        return false;
    }

}
